#include "laurin-device.h"
#include "laurin-utils.h"
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>  //form strncpy


LaurinDevice::LaurinDevice(const Glib::ustring& ifaceName)
{
	_ifaceName = Glib::ustring(ifaceName);

	// if not present it return 0
	_ifaceIndex = if_nametoindex(_ifaceName.c_str());

	_mac = NULL;
	_capture_context = NULL;
	_datalayer = -1;

	_initialized = retrieveMACAddress();
}


LaurinDevice::~LaurinDevice()
{
	if(_mac)
		free(_mac);

	if(_capture_context)
		closeCaptureContext();
}


bool LaurinDevice::setDeviceUp()
{
	if(!isDeviceUp())
		return setDeviceStatus(1);

	return true;
}


bool LaurinDevice::setDeviceDown()
{
	if(isDeviceUp())
		return setDeviceStatus(0);

	return true;
}


bool LaurinDevice::isDeviceUp()
{
    struct ifreq ifr;
    int sock;

    _errmess = "";

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
    	_errmess = Glib::ustring("Cannot open socket for device: ").append(_ifaceName);
    	return false;
    }

    strncpy (ifr.ifr_name, _ifaceName.c_str(), IFNAMSIZ);

    bool ret = true;
    if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0)
    {
    	_errmess = Glib::ustring("Error on setting flags for device: ").append(_ifaceName);
        ret = false;
    }
    else
    	ret = (ifr.ifr_flags & IFF_UP);

    close(sock);

    return ret;
}


bool LaurinDevice::setDeviceStatus(short flag)
{

	int sock;
	struct ifreq ifr;

	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		_errmess = Glib::ustring("Cannot open socket for device: ").append(_ifaceName);
		return false;
	}

	strncpy (ifr.ifr_name, _ifaceName.c_str(), IFNAMSIZ);

	if(flag)
		ifr.ifr_flags |= IFF_UP;
	else
		ifr.ifr_flags &= ~IFF_UP;

	bool ret = true;
	if(ioctl(sock, SIOCSIFFLAGS, &ifr) < 0)
	{
		_errmess = Glib::ustring("Error on setting flag for device: ").append(_ifaceName);
		ret = false;
	}

	close(sock);

	return ret;
}


bool LaurinDevice::retrieveMACAddress()
{
	int sock;
	struct ifreq ifr;

	_errmess = "";

	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		_errmess = Glib::ustring("Cannot open socket for device: ").append(_ifaceName);
		return false;
	}

	strncpy (ifr.ifr_name, _ifaceName.c_str(), IFNAMSIZ);

	if(ioctl(sock, SIOCGIFHWADDR, &ifr) == 0)
	{
		_mac = (u_int8_t*) malloc(ETH_ALEN);
		memcpy(_mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
	}
	else
		_errmess = Glib::ustring("Cannot retrieve mac address for device: ").append(_ifaceName);

	close(sock);

	return _errmess.empty();

}


void LaurinDevice::closeCaptureContext()
{
	if(_capture_context)
		pcap_close(_capture_context);

	_capture_context = NULL;
}


bool LaurinDevice::openCaptureContext()
{

	char errbuf[PCAP_ERRBUF_SIZE];
	memset(errbuf, 0, PCAP_ERRBUF_SIZE);

	if((_capture_context = pcap_create(_ifaceName.c_str(), errbuf)) == NULL)
	{
		_errmess = Glib::ustring(errbuf);
		return false;
	}

	if(!setCaptureOptions())
	{
		closeCaptureContext();
		return  false;
	}

	pcap_activate(_capture_context);

 	_datalayer = pcap_datalink(_capture_context);

 	return true;
}


bool LaurinDevice::setCaptureFilter(const Glib::ustring& filterSTM, int subnetMask)
{
	if(_capture_context != NULL)
	{
		struct bpf_program filter;	   // the bsd packet filter statement

		//set for optimizations
		if(pcap_compile(_capture_context, &filter, filterSTM.c_str(), 1, subnetMask) == -1)
		{
			_errmess = Glib::ustring("Error on compile capture filter for ").append(_ifaceName).append(" : ").append(pcap_geterr(_capture_context));
			return false;
		}


		if(pcap_setfilter(_capture_context, &filter) == -1)
		{
			_errmess = Glib::ustring("Error on set capture filter for ").append(_ifaceName).append(" : ").append(pcap_geterr(_capture_context));
			return false;
		}

		pcap_freecode(&filter);

		return true;
	}

	_errmess = Glib::ustring("Capture context is not open for ").append(_ifaceName);

	return false;
}


bool LaurinDevice::setCaptureOptions()
{
	// note that pcap_setdirection doesn't work
	pcap_set_timeout(_capture_context, 2000);
	return true;
}


pcap_t* LaurinDevice::getCaptureContext() const
{
	return _capture_context;
}


int LaurinDevice::getDeviceIndex() const
{
	return _ifaceIndex;
}


Glib::ustring LaurinDevice::getDeviceName() const
{
	return _ifaceName;
}


u_int8_t* LaurinDevice::getMAC() const
{
	return _mac;
}


int LaurinDevice::getDataLayerLink() const
{
	return _datalayer;
}


bool LaurinDevice::isInitialized() const
{
	return _initialized;
}


Glib::ustring LaurinDevice::getErrorMessage() const
{
	return _errmess;
}
